﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Kindohm.KSynth.Library;

namespace Kindohm.KSynth.UI
{
    public partial class SequencerGrid : UserControl
    {
        int stepCount;
        Dictionary<ButtonPoint, NoteButton> buttons;
        Sequencer sequencer;
        static readonly object stepLock = new object();
        List<StepPlayedEventArgs> stepList = new List<StepPlayedEventArgs>();
        DispatcherTimer timer;
        Rectangle cue;
        double leftMargin = 120;
        const int defaultOctave = 4;

        public SequencerGrid()
        {
            this.buttons = new Dictionary<ButtonPoint, NoteButton>();
            this.stepCount = 32;
            this.HorizontalSpacing = 3;
            this.VerticalSpacing = 3;
            this.HorizontalButtonSize = 20;
            this.VerticalButtonSize = 30;
            InitializeComponent();

            this.timer = new DispatcherTimer();
            this.timer.Interval = TimeSpan.FromMilliseconds(5);
            this.timer.Tick += new EventHandler(timer_Tick);
            this.timer.Start();
            this.Loaded += new RoutedEventHandler(SequencerGrid_Loaded);
        }

        public MediaElement Media { get; set; }

        void timer_Tick(object sender, EventArgs e)
        {
            if (this.Media != null)
            {
                TimeSpan mediaElementElapsedTime = this.Media.Position;

                lock (stepLock)
                {
                    for (int index = 0; index < this.stepList.Count; index++)
                    {
                        StepPlayedEventArgs args = this.stepList[index];

                        if (mediaElementElapsedTime >= args.ElapsedTime)
                        {
                            //staff.PositionIndex = args.PositionIndex;
                            this.cue.SetValue(Canvas.LeftProperty,
                                this.leftMargin + this.HorizontalSpacing + (args.Step - 1) *
                                (this.HorizontalSpacing + this.HorizontalButtonSize));

                            this.stepList.RemoveAt(index);
                            break;
                        }
                    }
                }
            }
        }

        public Sequencer Sequencer
        {
            get { return this.sequencer; }
            set
            {
                this.sequencer = value;
                this.sequencer.StepPlayed += new EventHandler<StepPlayedEventArgs>(sequencer_StepPlayed);
            }
        }

        public void Pause()
        {
            this.timer.Stop();
        }

        public void Start()
        {
            this.timer.Start();
        }

        void sequencer_StepPlayed(object sender, StepPlayedEventArgs e)
        {
            lock (stepLock)
            {
                this.stepList.Add(e);
            }
        }

        double HorizontalSpacing { get; set; }
        public double VerticalSpacing { get; set; }
        public double HorizontalButtonSize { get; set; }
        public double VerticalButtonSize { get; set; }

        public int StepCount
        {
            get { return this.stepCount; }
            set
            {
                this.stepCount = value;
                this.Sequencer.StepCount = this.stepCount;
                this.OnStepCountChanged();
            }
        }

        void SequencerGrid_Loaded(object sender, RoutedEventArgs e)
        {
            this.LoadNoteSettings();
            this.LoadGrid();
        }

        void OnStepCountChanged()
        {
            this.LoadGrid();
        }

        void LoadNoteSettings()
        {
            //note settings
            for (int y = 0; y < 8; y++)
            {
                NoteSettings settings = new NoteSettings();
                settings.YPosition = y;
                settings.SettingsChanged += new EventHandler(settings_SettingsChanged);
                this.noteCanvas.Children.Add(settings);
                settings.SetValue(Canvas.TopProperty,
                    this.VerticalSpacing + (double)y *
                            (this.VerticalSpacing + this.VerticalButtonSize));
                settings.SetValue(Canvas.LeftProperty,
                    0d);

                Note note = this.GetDefaultNoteFromYIndex(y);
                settings.Note = note;
                settings.Octave = defaultOctave;

            }
        }

        void settings_SettingsChanged(object sender, EventArgs e)
        {
            NoteSettings settings = (NoteSettings)sender;
            for (int x = 0; x < this.StepCount; x++)
            {
                ButtonPoint point = new ButtonPoint(x, settings.YPosition);
                if (this.buttons.ContainsKey(point))
                {
                    NoteButton button = this.buttons[point];
                    button.Note = settings.Note;
                    button.Octave = settings.Octave;

                    //hack - reset On value so that sequencer
                    //gets new pitch applied.
                    if (button.On)
                        button.On = true;
                }
            }
        }

        void LoadGrid()
        {
            for (int x = 0; x < this.StepCount; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    ButtonPoint point = new ButtonPoint() { X = x, Y = y };
                    if (!this.buttons.ContainsKey(point))
                    {
                        NoteButton button = new NoteButton() { ButtonPoint = point };
                        button.OnChanged += new EventHandler(button_OnChanged);
                        button.ButtonHeight = this.VerticalButtonSize;
                        button.ButtonWidth = this.HorizontalButtonSize;
                        button.ButtonPoint = point;
                        button.Note = this.GetDefaultNoteFromYIndex(y);
                        button.Octave = defaultOctave;
                        this.buttons.Add(point, button);
                        this.noteCanvas.Children.Add(button);
                        button.SetValue(Canvas.TopProperty,
                            this.VerticalSpacing + y *
                            (this.VerticalSpacing + this.VerticalButtonSize));
                        button.SetValue(Canvas.LeftProperty,
                            this.leftMargin + this.HorizontalSpacing + x *
                            (this.HorizontalSpacing + this.HorizontalButtonSize));
                    }
                    NoteButton noteButton = this.buttons[point];
                    noteButton.Visibility = Visibility.Visible;
                }
            }

            if (this.cue == null)
            {
                this.cue = new Rectangle();
                this.cue.Width = this.HorizontalButtonSize;
                this.cue.Height = 5;
                this.cue.Fill = new SolidColorBrush(Colors.White);
                this.noteCanvas.Children.Add(this.cue);
                this.cue.SetValue(Canvas.LeftProperty, this.leftMargin + this.HorizontalSpacing);
                this.cue.SetValue(Canvas.TopProperty, -this.VerticalSpacing);
            }

            foreach (ButtonPoint key in this.buttons.Keys)
            {
                if (this.buttons[key].ButtonPoint.X >= this.stepCount)
                {
                    this.buttons[key].Visibility = Visibility.Collapsed;
                }
            }

            if (this.Sequencer != null)
                this.Sequencer.StepCount = this.StepCount;
        }

        Note GetDefaultNoteFromYIndex(int y)
        {
            switch (y)
            {
                case 0:
                    return Note.C;
                case 1:
                    return Note.D;
                case 2:
                    return Note.E;
                case 3:
                    return Note.F;
                case 4:
                    return Note.G;
                case 5:
                    return Note.A;
                case 6:
                    return Note.B;
                case 7:
                    return Note.C;
            }
            throw new Exception("Unknown note");
        }

        void button_OnChanged(object sender, EventArgs e)
        {
            //turn off other buttons in same x position
            NoteButton button = (NoteButton)sender;
            if (button.On)
            {
                int xIndex = button.ButtonPoint.X;
                for (int y = 0; y < 8; y++)
                {
                    ButtonPoint point = new ButtonPoint(xIndex, y);
                    NoteButton foundButton = this.buttons[point];
                    if (foundButton != button)
                    {
                        foundButton.On = false;
                    }
                }
                this.Sequencer.SetStepPitch(xIndex + 1, new Pitch(button.Note, button.Octave));
            }
            else
            {
                this.Sequencer.SetStepPitch(button.ButtonPoint.X + 1, null);
            }
        }

    }
}
